#include <cstdio>
#include <iostream>

#include <boost/variant.hpp>

#define private public
#include "../src/sftparseworker.hh"
#undef private

namespace
{
  using namespace flassol;

  uint64_t
  lrand96 (void)
  {
    uint64_t rv;

    rv = lrand48 ();
    rv <<= 32;
    rv |= lrand48 ();

    return rv;
  }

  boost::optional<std::string>
  random_maybe_string ()
    {
      if (drand48 () < 0.2) 
        {
          return boost::none;
        }
      else
        {
          unsigned int len = 10.0 * drand48 ();
          std::string rv;

          for (unsigned int n = 0; n < len; ++n)
            {
              char c; do { c = lrand48 (); } while (c == 0);
              rv.push_back (c);
//              rv.push_back (' ' + ('~' - ' ') * drand48 ());
            }

          return rv;
        }
    }

  boost::shared_ptr<IndexedFeatureSet>
  random_feature_set ()
    {
      boost::shared_ptr<IndexedFeatureSet> f (new IndexedFeatureSet);
      size_t num_ns = lrand48 () % 16;

      for (size_t i = 0; i < num_ns; ++i)
        {
          uint8_t ns = 256.0 * drand48 ();
          unsigned int num_features = 16 * drand48 ();

          if (! (*f)[ns])
            {
              (*f)[ns] = FeatureSet ();
            }

          for (unsigned int n = 0; n < num_features; ++n)
            {
              unsigned int id = lrand96 ();
              float weight = drand48 () - 0.5;

              (*f)[ns]->push_back (Feature (id, weight));
            }
        }

      return f;
    }

  struct NullImportance
    {
    };

  struct DeltaImportance
    {
      unsigned int label;
      float        importance;

      DeltaImportance (unsigned int _label,
                       float        _importance)
        : label (_label),
          importance (_importance)
        {
        }
    };

  struct DenseImportance
    {
      std::vector<float> importance;

      DenseImportance (const std::vector<float>& _importance) 
        : importance (_importance)
        {
        }
    };

  struct SparseImportance
    {
      SparseVector importance;
      unsigned int k;

      SparseImportance (const SparseVector& _importance,
                        unsigned int        _k)
        : importance (_importance),
          k (_k)
        {
        }
    };

  struct ExampleSpecification
    {
      boost::variant<NullImportance,
                     DeltaImportance,
                     DenseImportance,
                     SparseImportance>          importance;

      boost::optional<std::string>              tag;
      boost::shared_ptr<IndexedFeatureSet>      f;
    };

  std::ostream&
  operator<< (std::ostream&             o,
              const NullImportance&)
    {
      return o << "(nullimportance)";
    }

  std::ostream&
  operator<< (std::ostream&             o,
              const DeltaImportance&    d)
    {
      return o << "delta (" << d.label << ", " << d.importance << ")";
    }

  std::ostream&
  operator<< (std::ostream&             o,
              const DenseImportance&    d)
    {
      return o << "dense (" << d.importance << ")";
    }

  std::ostream&
  operator<< (std::ostream&             o,
              const SparseImportance&   s)
    {
      return o << "sparse (" << s.importance << ", " << s.k << ")";
    }

  std::ostream&
  operator<< (std::ostream&               o,
              const ExampleSpecification& spec)
    {
      return o << "( importance = " << spec.importance 
               << " tag = " << spec.tag 
               << " f = " << spec.f 
               << ")";
    }

  ExampleSpecification
  random_null_spec (unsigned int)
    {
      ExampleSpecification spec;

      spec.tag = random_maybe_string ();
      spec.f = random_feature_set ();

      return spec;
    }

  ExampleSpecification
  random_delta_spec (unsigned int k)
    {
      ExampleSpecification spec;

      unsigned int label = k * drand48 ();
      float weight = (drand48 () < 0.1) ? -1.0 : drand48 () - 0.5;

      spec.importance = DeltaImportance (label, weight);
      spec.tag = random_maybe_string ();
      spec.f = random_feature_set ();

      return spec;
    }

  ExampleSpecification
  random_dense_spec (unsigned int k)
    {
      ExampleSpecification spec;
      std::vector<float> importance (k);

      for (unsigned int n = 0; n < k; ++n)
        {
          importance[n] = drand48 () - 0.5;
        }

      spec.importance = DenseImportance (importance);
      spec.tag = random_maybe_string ();
      spec.f = random_feature_set ();

      return spec;
    }

  ExampleSpecification
  random_sparse_spec (unsigned int k)
    {
      ExampleSpecification spec;
      SparseVector importance;

      do
        {
          for (unsigned int n = 0; n < k; ++n)
            {
              if (drand48 () < 0.5)
                {
                  importance[n] = drand48 () - 0.5;
                }
            }
        }
      while (importance.empty ());

      spec.importance = SparseImportance (importance, k);
      spec.tag = random_maybe_string ();
      spec.f = random_feature_set ();

      return spec;
    }

  boost::optional<ExampleSpecification>
  random_example_spec (unsigned int k)
    {
      double u = drand48 ();
      return (u < 0.1) ? boost::optional<ExampleSpecification> (boost::none) : 
             (u < 0.3) ? random_null_spec (k) :
             (u < 0.5) ? random_delta_spec (k) :
             (u < 0.7) ? random_dense_spec (k) :
                         random_sparse_spec (k);
    }

  struct CsmcFromSpecVisitor : public boost::static_visitor<boost::optional<SparseVector> >
    {
      boost::optional<SparseVector>
      operator() (const NullImportance&) const
        {
          return boost::none;
        }

      boost::optional<SparseVector>
      operator() (const DeltaImportance& imp) const
        {
          SparseVector sv;

          sv[imp.label] = imp.importance;

          return sv;
        }

      boost::optional<SparseVector>
      operator() (const DenseImportance& imp) const
        {
          SparseVector sv;

          for (unsigned int n = 0; n < imp.importance.size (); ++n)
            {
              sv[n] = imp.importance[n];
            }

          return sv;
        }

      boost::optional<SparseVector>
      operator() (const SparseImportance& imp) const
        {
          return imp.importance;
        }
    };

  boost::optional<CSMCExample> 
  csmc_from_spec (const boost::optional<ExampleSpecification>& spec)
    {
      if (spec)
        {
          CSMCExample csmc;

          csmc.tag = (spec->tag) ? const_cast<char*> (spec->tag->c_str ()) : 0;
          csmc.f = spec->f;
          csmc.cost = boost::apply_visitor (CsmcFromSpecVisitor (),
                                            spec->importance);

          return csmc;
        }
      else
        {
          return boost::none;
        }
    }

  class GeneralFromSpecVisitor : public boost::static_visitor<>
    {
      private:
        GeneralExample& example;
        char*           bufone;
        char*           buftwo;

      public:
        GeneralFromSpecVisitor (GeneralExample& _example,
                                char*           _bufone,
                                char*           _buftwo) : example (_example),
                                                           bufone (_bufone),
                                                           buftwo (_buftwo)
          {
          }

      void
      operator() (const NullImportance&) const
        {
          example.label = 0;
          example.importance = 0;
        }

      void
      operator() (const DeltaImportance& imp) const
        {
          snprintf (bufone, 1024, "%u", imp.label);
          example.label = bufone;

          if (imp.importance == -1)
            {
              example.importance = 0;
            }
          else
            {
              snprintf (buftwo, 1024, "%g", -imp.importance);
              example.importance = buftwo;
            }
        }

      void
      operator() (const DenseImportance& imp) const
        {
          char* buf = buftwo;
          unsigned int len = 1024;

          example.label = 0;

          for (unsigned int n = 0; n < imp.importance.size (); ++n)
            {
              int rv = snprintf (buf,
                                 len,
                                 "%s%g",
                                 (n == 0) ? "" : ",",
                                 -imp.importance[n]);

              assert (rv > 0);

              buf += rv;
              len -= rv;
            }

          example.importance = buftwo;
        }

      void
      operator() (const SparseImportance& imp) const
        {
          char* buf = buftwo;
          unsigned int len = 1024;

          example.label = 0;

          for (unsigned int n = 0; n < imp.k; ++n)
            {
              if (imp.importance[n])
                {
                  int rv = snprintf (buf,
                                     len,
                                     "%s%g:%u",
                                     (n == 0) ? "" : ",",
                                     -static_cast<float> (imp.importance[n]),
                                     n);

                  assert (rv > 0);

                  buf += rv;
                  len -= rv;
                }
            }

          example.importance = buftwo;
        }
    };

  boost::optional<GeneralExample>
  general_from_spec (const boost::optional<ExampleSpecification>& spec,
                     char*                                        bufone,
                     char*                                        buftwo)
    {
      if (spec)
        {
          GeneralExample example;

          boost::apply_visitor 
            (GeneralFromSpecVisitor (example, bufone, buftwo),
             spec->importance);
          example.tag = 
            (spec->tag) ? const_cast<char*> (spec->tag->c_str ()) : 0;
          example.f = spec->f;

          return example;
        }
      else
        {
          return boost::none;
        }
    }

  bool
  fuzzy_eq (const float& a,
            const float& b)
    {
      return (fabs (a - b) <= 1e-5 * (1.0 + fabs (a) + fabs (b))) ||
             (std::cerr << a << " != " << b << std::endl, 0);
    }

  bool
  operator== (const SparseVector& a,
              const SparseVector& b)
    {
      for (SparseVector::const_iterator x = a.begin (); x != a.end (); ++x)
        {
//          std::cerr << "x->second = " << x->second << " b[" << x->first << "] = " << b[x->first] << std::endl;

          if (! fuzzy_eq (x->second, b[x->first]))
            {
              return false;
            }
        }

      for (SparseVector::const_iterator x = b.begin (); x != b.end (); ++x)
        {
//          std::cerr << "x->second = " << x->second << " a[" << x->first << "] = " << a[x->first] << std::endl;

          if (! fuzzy_eq (x->second, a[x->first]))
            {
              return false;
            }
        }

      return true;
    }

  bool
  operator== (const CSMCExample& a,
              const CSMCExample& b)
    {
//      std::cerr << "a.importance == b.importance ? " << (a.importance == b.importance) << std::endl;
//      std::cerr << "'" << a.tag << "' (" << static_cast<void*> (a.tag) << ") = a.tag =?= b.tag = '" << b.tag << "' (" << static_cast<void*> (b.tag) << ")" << std::endl;
//      std::cerr << a.f << " = a.f =?= b.f = " << b.f << std::endl;
//
//      std::cerr << "tag comparison = " 
//                << ((! a.tag) != (! b.tag) 
//                      ? false 
//                      : (! a.tag ? true : strcmp (a.tag, b.tag) == 0))
//                << std::endl;
//      std::cerr << "f comparison = " 
//                << (a.f == b.f)
//                << std::endl;
//
//      std::cerr << "overall comparison = " 
//                << ( (a.importance == b.importance) &&
//                     ((! a.tag) != (! b.tag) 
//                        ? false 
//                        : (! a.tag ? true : strcmp (a.tag, b.tag) == 0)) &&
//                     a.f == b.f )
//                << " " 
//                << (a.importance == b.importance) 
//                << " , "
//                << ((! a.tag) != (! b.tag) 
//                      ? false 
//                      : (! a.tag ? true : strcmp (a.tag, b.tag) == 0))
//                << " , " 
//                << (a.f == b.f)
//                << std::endl;

//      return (a.cost == b.cost) &&
      return (((! a.cost) != (! b.cost))
                ? false
                : (! a.cost ? true : *(a.cost) == *(b.cost))) &&
             ((! a.tag) != (! b.tag) 
                ? false 
                : (! a.tag ? true : strcmp (a.tag, b.tag) == 0)) &&
             a.f == b.f;
    }

  void
  test_from_general_example_once ()
    {
      char bufone[1024];
      char buftwo[1024];
      unsigned int k = 1 + 10 * drand48 ();
      boost::optional<ExampleSpecification> spec = random_example_spec (k);

      boost::optional<GeneralExample> example = general_from_spec (spec,
                                                                   bufone,
                                                                   buftwo);
      boost::optional<CSMCExample> csmc = 
        SftParseWorker::from_general (example, k, 1, false);

      boost::optional<CSMCExample> direct = csmc_from_spec (spec);

      assert ((((! csmc) != (! direct))
                ? false
                : (! csmc ? true : (*csmc) == (*direct)) ) ||
              (std::cerr << "(csmc (" 
                         << (static_cast<bool> (csmc) ? "exists" : "undef" )
                         << ") == direct (" 
                         << (static_cast<bool> (direct) ? "exists" : "undef" )
                         << ")) = " 
                         << (((! csmc) != (! direct))
                             ? false
                             : (! csmc ? true : (*csmc) == (*direct)))
                         << std::endl,
               std::cerr << "example spec = " << *spec << std::endl,
               0));
    }

  void
  test_from_general_example ()
    {
      for (unsigned int n = 0; n < 1000; ++n)
        {
          test_from_general_example_once ();
        }
    }
}

int 
main (void)
{
  srand48 (69);

  test_from_general_example ();

  return 0;
}
